﻿Imports System.Collections.Generic
Imports System.Linq
Imports System.Xml.Serialization


''' <summary>
''' Defines a database table entity.
''' </summary>
Public Class Table

    Public Sub New()
        ForeignKeys = New List(Of ForeignKey)()
        Columns = New List(Of Column)()
        HasManyRelationships = New List(Of HasMany)()
        Me.ReferencingTables = New List(Of Reference)
    End Sub

    <XmlElement(ElementName:="TableName", IsNullable:=False, Type:=GetType(String))>
    Public Property Name() As String

    <XmlElement(ElementName:="Owner", IsNullable:=False, Type:=GetType(String))>
    Public Property Owner() As String

    <XmlElement("PrimaryKey")>
    Public Property PrimaryKey() As PrimaryKey

    <XmlArray("ForeignKeys")>
    Public Property ForeignKeys() As List(Of ForeignKey)

    <XmlArray("Columns")>
    Public Property Columns() As List(Of Column)

    <XmlArray("HasManyRelationships")>
    Public Property HasManyRelationships() As List(Of HasMany)

    'Keeps references to current table
    <XmlArray("ReferencingTables")>
    Public Property ReferencingTables() As List(Of Reference)


    ''' <summary>
    ''' Given a column from this table, if it is a foreign key return the name of the table it references.
    ''' </summary>
    Public Function ForeignKeyReferenceForColumn(column As Column) As String
        If ForeignKeys IsNot Nothing Then
            Dim fKey = ForeignKeys.Where(Function(fk) fk.Name = column.Name).FirstOrDefault()
            If fKey IsNot Nothing Then
                Return fKey.References
            End If
        End If
        Return [String].Format("/* TODO: UNKNOWN FOREIGN ENTITY for column {0} */", column.Name)
    End Function

    Public Overrides Function ToString() As String
        Return Name
    End Function

    ''' <summary>
    ''' When one table has multiple fields that represent different relationships to the same foreign entity, it is required to give them unique names.
    ''' </summary>
    Public Shared Sub SetUniqueNamesForForeignKeyProperties(foreignKeys As IEnumerable(Of ForeignKey))
        Dim referencesUsedMoreThanOnce = foreignKeys.[Select](Function(f) f.References).Distinct().GroupJoin(foreignKeys, Function(a) a, Function(b) b.References, Function(a, b) New With { _
         Key .References = a, _
         Key .Count = b.Count() _
        }).Where(Function(t) t.Count > 1).[Select](Function(t) t.References)

        foreignKeys.Join(referencesUsedMoreThanOnce, Function(a) a.References, Function(b) b, Function(a, b) a).ToList().ForEach(Sub(fk)
                                                                                                                                     fk.UniquePropertyName = Convert.ToString(fk.Name) & "_" & Convert.ToString(fk.References)
                                                                                                                                 End Sub)
    End Sub

End Class

Public Class Reference
    Public Property FKName As String = ""
    Public Property FKTableName As String = ""
    Public Property FKColumn As String = ""
    Public Property RefColumn As String = ""

    Public Sub New()

    End Sub

    Public Sub New(FKN As String, FKT As String, FKC As String, RC As String)
        Me.FKName = FKN
        Me.FKTableName = FKT
        Me.FKColumn = FKC
        Me.RefColumn = RC
    End Sub

End Class

Public Class HasMany
    Public Sub New()
        AllReferenceColumns = New List(Of String)()
    End Sub

    ''' <summary>
    ''' An identifier for a constraint so that we might detect from querying the database whether a relationship has one is a composite key.
    ''' </summary>
    Public Property ConstraintName() As String
        Get
            Return m_ConstraintName
        End Get
        Set(value As String)
            m_ConstraintName = value
        End Set
    End Property
    Private m_ConstraintName As String

    Public Property Reference() As String
        Get
            Return m_Reference
        End Get
        Set(value As String)
            m_Reference = value
        End Set
    End Property

    Private m_Reference As String

    ''' <summary>
    ''' In support of relationships that use composite keys.
    ''' </summary>
    Public Property AllReferenceColumns() As List(Of String)
        Get
            Return m_AllReferenceColumns
        End Get
        Set(value As List(Of String))
            m_AllReferenceColumns = value
        End Set
    End Property

    Private m_AllReferenceColumns As List(Of String)

    ''' <summary>
    ''' Provide the first (and very often the only) column used to define a foreign key relationship.
    ''' </summary>
    Public Property ReferenceColumn() As String
        Get
            Return If(AllReferenceColumns.Count > 0, AllReferenceColumns(0), "")
        End Get
        Set(value As String)
            AllReferenceColumns = New List(Of String)() From { _
             value _
            }
        End Set
    End Property

End Class

''' <summary>
''' Defines a database column entity;
''' </summary>
Public Class Column

    Public Property Name() As String
    Public Property IsPrimaryKey() As Boolean
    Public Property IsForeignKey() As Boolean
    Public Property IsUnique() As Boolean
    Public Property DataType() As String
    Public Property DataLength() As System.Nullable(Of Integer)
    Public Property MappedDataType() As String
    Public Property IsNullable() As Boolean
    Public Property ConstraintName() As String
    Public Property DataPrecision() As System.Nullable(Of Integer)
    Public Property DataScale() As System.Nullable(Of Integer)

End Class

Public Interface IPrimaryKey

    ReadOnly Property KeyType() As PrimaryKeyType
    Property Columns() As List(Of Column)

End Interface

Public MustInherit Class AbstractPrimaryKey
    Implements IPrimaryKey

    Protected Sub New()
        Columns = New List(Of Column)()
    End Sub

#Region "IPrimaryKey Members"

    Public MustOverride ReadOnly Property KeyType() As PrimaryKeyType Implements IPrimaryKey.KeyType
    Public Property Columns() As List(Of Column) Implements IPrimaryKey.Columns


#End Region
End Class

''' <summary>
''' Defines a primary key entity.
''' </summary>
Public Class PrimaryKey

    Public Sub New()
        Columns = New List(Of Column)()
    End Sub

    Public Property Type() As PrimaryKeyType
    Public Property Columns() As List(Of Column)
    Public Property IsGeneratedBySequence() As Boolean
    Public Property IsSelfReferencing() As Boolean ' Oracle only.

End Class

''' <summary>
''' Defines a composite key entity.
''' </summary>
Public Class CompositeKey
    Inherits AbstractPrimaryKey

    Public Overrides ReadOnly Property KeyType() As PrimaryKeyType
        Get
            Return PrimaryKeyType.CompositeKey
        End Get
    End Property

End Class

''' <summary>
''' Defines a foreign key entity.
''' </summary>
Public Class ForeignKey
    ''' <summary>
    ''' Foreign key column name.
    ''' </summary>
    Public Property Name() As String
    ''' <summary>
    ''' Defines what table the foreign key references.
    ''' </summary>
    Public Property References() As String
    Private _uniquePropertyName As String
    ''' <summary>
    ''' When one table has multiple fields that represent different relationships to the same foreign entity, it is required to give them unique names.
    ''' </summary>
    Public Property UniquePropertyName() As String
        Get
            Return If(String.IsNullOrEmpty(_uniquePropertyName), References, _uniquePropertyName)
        End Get
        Set(value As String)
            _uniquePropertyName = value
        End Set
    End Property
    ''' <summary>
    ''' A foreign key may be one of multiple columns of a composite key to a foreign entity
    ''' </summary>
    Public Property AllColumnsNamesForTheSameConstraint() As String()

    Public Overrides Function ToString() As String
        Return Name
    End Function

End Class

